﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Resources;
using System.Threading;

namespace Goldlight.Mvvm.Base.Infrastructure
{
    /// <summary>
    /// This class manages the validation that's used throughout the application.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class Validation<T>
        where T : class
    {
        #region Members
        private static Dictionary<T, Dictionary<string, List<ValidationRule<T>>>> _rules;
        private static Dictionary<T, string> _resoureFilename = new Dictionary<T,string>();
        #endregion

        #region Add validation
        /// <summary>
        /// Add in the validation rule.
        /// </summary>
        public static void AddValidation(T instance, string property, Func<T, bool> validationRule, string message)
        {
            AddValidation(instance, property, new ValidationRule<T> { Message = message, Rule = validationRule });
        }
        /// <summary>
        /// Add in the validation rule.
        /// </summary>
        public static void AddValidation(T instance, string property, ValidationRule<T> validationRule)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");
            if (string.IsNullOrEmpty(property))
                throw new ArgumentException("property");
            if (validationRule == null)
                throw new ArgumentNullException("validationRule");
            if (validationRule.Rule == null)
                throw new ArgumentNullException("validationRule.Rule");
            if (string.IsNullOrEmpty(validationRule.Message))
                throw new ArgumentException("validationRule.Message");

            if (_rules == null)
                _rules = new Dictionary<T, Dictionary<string, List<ValidationRule<T>>>>();
            if (!_rules.ContainsKey(instance))
            {
                Dictionary<string, List<ValidationRule<T>>> rules = new Dictionary<string, List<ValidationRule<T>>>();
                _rules.Add(instance, rules);
            }

            if (!_rules[instance].ContainsKey(property))
            {
                List<ValidationRule<T>> rules = new List<ValidationRule<T>>();
                _rules[instance].Add(property, rules);
            }

            Dictionary<string, List<ValidationRule<T>>> candidateRules = _rules[instance];
            candidateRules[property].Add(validationRule);
        }
        #endregion

        #region Remove rule
        /// <summary>
        /// Remove a rule from the list.
        /// </summary>
        /// <param name="instance">The instance to remove the rule from.</param>
        /// <param name="property">The property to remove the rule from.</param>
        /// <param name="validationRule">The rule to remove.</param>
        public static void RemoveRule(T instance, string property, ValidationRule<T> validationRule)
        {
            Dictionary<string, List<ValidationRule<T>>> candidateRules = _rules[instance];
            candidateRules[property].Remove(validationRule);
        }

        /// <summary>
        /// Remove a rule from the list.
        /// </summary>
        /// <param name="instance">The instance to remove the rule from.</param>
        /// <param name="property">The property to remove the rule from.</param>
        /// <param name="validationRule">The rule to remove.</param>
        public static void RemoveRule(T instance, string property, Func<T, bool> validationRule)
        {
            Dictionary<string, List<ValidationRule<T>>> candidateRules = _rules[instance];
            List<ValidationRule<T>> rules = candidateRules[property];
            for (int i = 0; i < rules.Count; i++)
            {
                if (rules[i].Rule == validationRule)
                {
                    rules.RemoveAt(i);
                }
            }
        }

        /// <summary>
        /// Remove all rules from a property.
        /// </summary>
        /// <param name="instance">The instance to remove the rule from.</param>
        /// <param name="property">The property to remove the rule from.</param>
        public static void RemoveRules(T instance, string property)
        {
            Dictionary<string, List<ValidationRule<T>>> candidateRules = _rules[instance];
            candidateRules.Remove(property);
        }
        #endregion

        public static void RegisterResource(T instance, string resourceFile)
        {
            if (!_resoureFilename.ContainsKey(instance))
            {
                _resoureFilename.Add(instance, resourceFile);
            }
        }

        public static void UnregisterResource(T instance, string resourceFile)
        {
            if (_resoureFilename.ContainsKey(instance))
            {
                _resoureFilename.Remove(instance);
            }
        }
        /// <summary>
        /// Perform the validation.
        /// </summary>
        /// <param name="instance">The instance to perform the validation on.</param>
        /// <param name="property">The property to validate.</param>
        /// <returns>An empty string if the validation passes. Any failures are reported back through this.</returns>
        public static string Validate(T instance, string property)
        {
            StringBuilder sb = new StringBuilder();
            List<string> failedRules = new List<string>();
            Dictionary<string, List<ValidationRule<T>>> candidateRules = _rules[instance];

            // If there are no rules for this key, we need to return an empty string because this means
            // we can't have any failures.
            if (!candidateRules.ContainsKey(property))
                return string.Empty;

            foreach (ValidationRule<T> rule in candidateRules[property])
            {
                if (!rule.Rule(instance))
                {
                    // Attempt to get the message from the relevant resource file.
                    string message = GetMessage(instance, rule.Message);
                    sb.AppendFormat("{0}{1}", message, Environment.NewLine);
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Get the validation failure message from the relevant resource file.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private static string GetMessage(T instance, string message)
        {
            try
            {
                if (_resoureFilename.ContainsKey(instance))
                {
                    ResourceManager mgr = new ResourceManager(_resoureFilename[instance], instance.GetType().Assembly);
                    object value = mgr.GetString(message, Thread.CurrentThread.CurrentCulture); // new ResourceManager(typeof(T)).GetObject(message);
                    if (value != null)
                    {
                        message = value.ToString();
                    }
                }
            }
            catch { }
            return message;
        }
    }

    public class ValidationRule<T>
    {
        public Func<T, bool> Rule;
        public string Message;
    }
}
